#ifndef __SLOTHTTPD_GENERIC_POOL_HPP__
#define __SLOTHTTPD_GENERIC_POOL_HPP__

#include <utility>

#include <boost/noncopyable.hpp>
#include <boost/intrusive/slist.hpp>

namespace slothttpd {

class generic_pool :
        public boost::noncopyable
{
private:
    class pool_header :
            public boost::noncopyable
    {
        class cleanup_type;
        class large_block_type;
        
        pool_header(std::size_t size) noexcept;
        ~pool_header() = delete;
    public:
        static pool_header *create_block(std::size_t size);
        // returns nullptr on failure
        void *allocate(std::size_t size) noexcept;
        void reset() noexcept;
        void destroy() noexcept;
        
    public:
        char *begin() noexcept;
        char *end() noexcept;
        
        const std::size_t       size_;
        char                   *last_;
        pool_header            *next_;
    };
    
    struct cleanup_type :
            public boost::intrusive::slist_base_hook<>
    {
    public:
        inline cleanup_type() : handler(nullptr) { }
        void clean() noexcept;
        
        void (*handler)(void *) noexcept;
        void *arg;
    };
    
    struct large_block_type :
            public boost::intrusive::slist_base_hook<>
    {
        char *data;
    };
    
public:
    static const std::size_t alignment;
    
    static char *align_ptr(char *p, std::size_t a);
    
    generic_pool(std::size_t size);
    void reset() noexcept;
    void free_pool() noexcept;
    ~generic_pool() noexcept;
    
    // both throw std::bad_alloc on failure
    void *allocate(std::size_t size);
    void *allocate_aligned(std::size_t size, std::size_t align);
    void post_cleanup(void (*handler)(void*), void *arg);
    
    void free(void *p) noexcept;
    
    template<typename T, typename... Arguments>
    T *construct(Arguments&&... args)
    {
        auto p = static_cast<T*>(allocate(sizeof(T)));
        new (p) T(std::forward<Arguments>(args)...);
        return p;
    }
    
    /**
     * Same as construct, but also calls destructor on pool destruction\freeing\resetting
     */
    template<typename T, typename... Arguments>
    T *construct_managed(Arguments&&... args)
    {
        cleanup_type   *cleanup = construct<cleanup_type>();
        T              *p  = construct<T>(std::forward<Arguments>(args)...);
        
        class cln
        {
        public:
            static void clean(void *arg)
            {
                static_cast<T*>(arg)->~T();
            }
        };

        cleanup->handler    = &cln::clean;
        cleanup->arg        = static_cast<void*>(p);

        cleanups_.push_front(*cleanup);

        return p;
    }
    
private:
    // both throw std::bad_alloc on failure
    void *allocate_small(std::size_t size);
    void *allocate_large(std::size_t size);
    
private:
    typedef boost::intrusive::slist<cleanup_type>       cleanup_list_t;
    typedef boost::intrusive::slist<large_block_type>   large_block_list_t;
    
    const std::size_t               block_size_;
    const std::size_t               max_alloc_;
    
    pool_header                    *first_;
    pool_header                    *current_;
    
    cleanup_list_t                  cleanups_;
    large_block_list_t              large_blocks_;
};


} //namespace slothttpd

#endif
